﻿/// <summary>
/// Implements SolarSystem class.
/// </summary>
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SpaceSim.External;
using System.Xml;
using System.Xml.Serialization;

namespace SpaceSim
{
    /// <summary>
    /// Represents an individual SolarSystem in the simulation.
    /// </summary>
    [XmlRootAttribute()]
    [Serializable]
    public class SolarSystem
    {
        public const string PreferredFileExtension = ".solarsystem.xml";

        #region Public members
        [XmlAttribute()]
        public string Name { get; set; }
        
        [XmlElement()]
        public AstroBody Primary { get; set; }

        [XmlElement("Planet")]
        public List<AstroBody> AstroBodies { get; set; }

        [XmlIgnore()]
        public int AstroBodyCount { get { return AstroBodies != null ? AstroBodies.Count : 0; } }
        #endregion

        /// <summary>
        /// Constructs as object devoid of any astrophysics bodies
        /// </summary>
        public SolarSystem()
        {
            AstroBodies = new List<AstroBody>();
        }

        /// <summary>
        /// Constructs fully configured using given values.
        /// </summary>
        /// <param name="name">Name for SolarSystem.</param>
        /// <param name="primary">AstroBody to use as SolarSystem's primary.</param>
        /// <param name="bodies">AstroBodies to use to fill Solarsystem with.</param>
        public SolarSystem(string name, AstroBody primary, List<AstroBody> bodies)
            : this()
        {
            Name = name;
            Primary = primary;
            AstroBodies = bodies;
        }

        /// <summary>
        /// Constructs randomly using AstroBodyDescs from given glossary
        /// </summary>
        /// <param name="inGlossary">Glossary used to build SolarSystem from.</param>
        public SolarSystem(Glossary inGlossary)
            : this()
        {
            // Make our own copy of the given value and invoke all its modifiers once.
            Glossary glossary = SpaceSim.External.Misc.DeepClone(inGlossary);
            glossary.ModifiersInitialize(this);

            // Build system name
            Name = _BuildSolarSystem_Name(glossary, 4, 5);

            // Pick the system's primary
            {
                AstroBodyDesc[] potentials = glossary.AstroBodyDescs.Where(potentialDesc => potentialDesc.Primary).ToArray();
                AstroBodyDesc desc = _BuildSolarSystem_PickAstroBodyDesc(glossary, potentials);
                Primary = new AstroBody(Name + " Prime", SpaceSim.External.Misc.DeepClone(desc));

                glossary.ModifiersOnSelection(this, Primary.Desc);
            }

            // Pick the panetoids
            for (int index = 0; index < glossary.PlanetCount; ++index)
            {
                AstroBodyDesc[] potentials = glossary.AstroBodyDescs.Where(potentialDesc => !potentialDesc.Primary).ToArray();
                AstroBodyDesc desc = _BuildSolarSystem_PickAstroBodyDesc(glossary, potentials);
                if (desc == null)
                {
                    continue;
                }

                AstroBody astroBody = new AstroBody(Name + " " + (AstroBodies.Count + 1).ToString(), SpaceSim.External.Misc.DeepClone(desc));
                AstroBodies.Add(astroBody);
                desc.Probability -= (desc.Probability * glossary.SelectionPenalty);

                glossary.ModifiersOnSelection(this, astroBody.Desc);
            }
        }

        /// <summary>
        /// Builds a random name for a SolarSystem.
        /// </summary>
        /// <param name="inGlossary">Glossary used to build SolarSystem from.</param>
        /// <param name="maxLetters">Maximum count of letters to use in name.</param>
        /// <param name="maxNumbers">Maximum count of numbers to use in name.</param>
        public static string _BuildSolarSystem_Name(Glossary glossary, int maxLetters, int maxNumbers)
        {
            string result = "";

            // Prefix
            {
                string prefixes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                int numLetters = glossary.Rand.Next(1, maxLetters);
                for (int index = 0; index < numLetters; ++index)
                {
                    result += prefixes[glossary.Rand.Next(prefixes.Length - 1)];
                }
            }

            // Postfix
            {
                int numNumbers = glossary.Rand.Next(2, maxNumbers);
                for (int index = 0; index < numNumbers; ++index)
                {
                    result += glossary.Rand.Next(0, 9).ToString();
                }
            }

            return result;
        }

        /// <summary>
        /// Picks a random AstroBodyDesc to stock a SolarSystem with from the given list of bodies.
        /// 
        /// Will take into account AstroBodyDesc.Probability.
        /// </summary>
        /// <param name="inGlossary">Glossary used to build SolarSystem from.</param>
        /// <param name="bodies">List of AstroBodyDescs to pick from.</param>
        /// <return>Randomly selected object from bodies list.</param>
        public static AstroBodyDesc _BuildSolarSystem_PickAstroBodyDesc(Glossary glossary, AstroBodyDesc[] bodies)
        {
            if (bodies == null || bodies.Length < 1)
            {
                return null;
            }

            double probabilitySum = bodies.Sum(desc => desc.Probability);
            if (probabilitySum <= 0.0)
            {
                return null;
            }

            double rand = glossary.Rand.NextDouble() * probabilitySum;
            double runningSum = 0;
            foreach (AstroBodyDesc desc in bodies)
            {
                if (desc.Probability > 0.0)
                {
                    runningSum += desc.Probability;
                    if (rand <= runningSum)
                    {
                        return desc;
                    }
                }
            }

            // Fall back. Ignore weight and just return a random element from the array.
            return bodies[glossary.Rand.Next(0, bodies.Length - 1)];
        }
    }
}
